<template lang="pug">
q-uploader(auto-upload
:accept="accept"
  :readonly="readOnly"
  :max-file-size="computedMaxFileSize"
  :max-files="maxFiles"
  class="full-width"
  :flat="readOnly"
  @added="beforeUpload"
  @rejected="fileRejected"
  @uploaded="fileUploaded"
  @removed="fileRemoved"
  @failed="fileFailed"
  :filter="checkFiles"
  ref="uploader"
  :factory="factoryFn"
  multiple
  v-show="isShowUploadHeader")
  template(v-slot:header="scope")
    div(v-if="!readOnly")
      q-toolbar(class="row no-wrap items-center q-pa-sm q-gutter-xs")
        q-btn(v-if="!readOnly && uploadedFiles.length>0" icon="r_delete" @click="removeAllFile" round dense flat )
          q-tooltip 移除所有文件
        q-spinner(v-if="scope.isUploading" class="q-uploader__spinner")
        div(class="col")
          div(class="q-uploader__title q-mx-md") {{ title }}
          div(class="q-uploader__subtitle" v-if="scope.isUploading" ) {{ scope.uploadSizeLabel }} / {{ scope.uploadProgressLabel }}
        q-btn(v-if="scope.canAddFiles" icon="add_box" @click="scope.pickFiles" round dense flat )
          q-uploader-add-trigger
          q-tooltip 选择文件(总大小 {{ format.humanStorageSize(computedMaxFileSize) }} 以内, 支持 {{ accept && accept!='*/*' ? accept : '所有' }} 格式)
        q-btn(v-if="scope.canUpload" icon="cloud_upload" @click="scope.upload" round dense flat )
          q-tooltip 上传文件
        q-btn(v-if="scope.isUploading" icon="clear" @click="scope.abort" round dense flat )
          q-tooltip 中止上传
  template(v-slot:list="scope")
    div(class="rounded-borders bg-grey-2")
      div(v-for="(item, index) in uploadedFiles"  :key="item.file_name + index")
        q-bar(class="bg-grey-2 full-width rounded-borders" square)
          q-btn(color="primary" flat dense icon="fa-solid fa-eye" v-if="canPreview" @click="previewUpload(item)")
            q-tooltip( anchor="bottom start" self="top left" ) 预览
          q-btn(color="primary" flat dense icon="r_sim_card_download" @click="downloadUpload(item)")
            q-tooltip( anchor="bottom start" self="top left" ) 下载
          q-btn(color="primary" v-if="!readOnly && canDelete" flat dense icon="r_delete" @click="removeUpload(item)")
            q-tooltip( anchor="bottom start" self="top left" ) 删除
          div {{ item.file_name }}
            q-tooltip( anchor="bottom start" self="top left" ) {{ item.file_name }}
      q-inner-loading(:showing="loading" class="z-top")
        q-spinner-hourglass(color="primary" size="30px")
template(v-if="isReduce")
  div(style="display:block;width:100%;")
    q-btn(color='primary' @click='uploadHandle'  icon="add_box") 上传文件
  div(class="rounded-borders bg-grey-2" :style="{marginTop:uploadedFiles.length<=0?0:'10px'}")
    div(v-for="(item, index) in uploadedFiles"  :key="item.file_name + index")
      q-bar(class="bg-grey-2 full-width rounded-borders" square)
        q-btn(color="primary" flat dense icon="fa-solid fa-eye" v-if="canPreview" @click="previewUpload(item)")
          q-tooltip( anchor="bottom start" self="top left" ) 预览
        q-btn(color="primary" flat dense icon="r_sim_card_download" @click="downloadUpload(item)")
          q-tooltip( anchor="bottom start" self="top left" ) 下载
        q-btn(color="primary" v-if="!readOnly && canDelete" flat dense icon="r_delete" @click="removeUpload(item)")
          q-tooltip( anchor="bottom start" self="top left" ) 删除
        div {{ item.file_name }}
          q-tooltip( anchor="bottom start" self="top left" ) {{ item.file_name }}
    q-inner-loading(:showing="loading" class="z-top")
      q-spinner-hourglass(color="primary" size="30px")
</template>

<script setup>

import {is, Cookies, debounce, useQuasar, format, extend,} from "quasar";
import {ref, watch, computed} from "vue";
import {api_delete_files, api_download_file, api_get_file_list} from "~/apis/common/file_apis.js";

const $q = useQuasar()

const uploader = ref(null)


const props = defineProps({
  title: {
    type: String,
    default: '文件列表'
  },
  uploadParams: {
    type: Object,
    default: () => {
      return {
        path: 'common',
        file_desc: '',
        file_tag: ''
      }
    }
  },
  fieldName:{
    type: String,
    default: 'file'
  },
  modelValue: {
    type: Array,
    default: []
  },
  readOnly: {
    type: Boolean,
    default: false
  },
  canDelete: {
    type: Boolean,
    default: true
  },
  accept: {
    type: String,
    default: '*/*'
  },
  maxFileSize: {
    type: [String, Number],
    default: 1024 * 1024 * 100
  },
  checkFiles: {
    type: Function,
    default: (files) => files
  },
  maxFiles: {
    type: Number,
    default: 1000
  },
  isImport: {
    type: Boolean,
    default: false
  },
  isReduce: {
    type: Boolean,
    default: false
  },
  canPreview: {
    type: Boolean,
    default: false
  }
})

const computedMaxFileSize = computed(() => {
  // 如果props.maxFileSize为数字，则直接返回
  if (is.number(props.maxFileSize)) return props.maxFileSize
  // 用 * 分隔
  const parts = props.maxFileSize.split('*')
  // 各部分相乘
  return parts.reduce((acc, val) => acc * val, 1)
})

const emit = defineEmits(['update:modelValue', 'check-files', 'afterUploaded', 'fileRemoved'])


// 点击删除按钮触发
const removeUpload = (file) => {
  // 确认删除
  $q.dialog({
    title: '删除文件确认',
    message: '确定要删除该文件吗？',
    cancel: true,
    persistent: false
  }).onOk(() => {
    api_delete_files({id: file.id}).then(() => {
      uploadedFiles.value = uploadedFiles.value.filter(item => item.id !== file.id)
      emit('update:modelValue', uploadedFiles.value.map(item => item.id))
    })
  })
}

// 点击下载按钮触发
const downloadUpload = (file) => {
  api_download_file({id: file.id, file_name: file.file_name}).then()
}

// 点击预览按钮触发
const previewUpload = (file) => {
  window.open(`/api/v1.0/base_info/sys_file/${file.id}/preview/`)
}

// 点击删除所有按钮触发
const removeAllFile = () => {
  $q.dialog({
    title: '删除文件确认',
    message: '确定要删除所有文件吗？',
    cancel: true,
    persistent: false
  }).onOk(() => {
    api_delete_files({id__in: props.modelValue}).then(() => {
      uploadedFiles.value = []
      emit('update:modelValue', [])
    })
  })
}

// 上传配置
const factoryFn = () => {
  return {
    fieldName: props.fieldName,
    url: "/api/v1.0/file_manage/upload/",
    method: "POST",
    headers: [{
      name: 'Authorization',
      value: 'Bearer ' + Cookies.get('access')
    }],
    formFields: [
      {
        name: 'path',
        value: props.uploadParams.path
      },
      {
        name: 'file_desc',
        value: props.uploadParams.file_desc
      },
      {
        name: 'file_tag',
        value: props.uploadParams.file_tag
      },
    ],
  }
}

const file_id_list = computed({
  get() {
    if (!props.modelValue) return []
    return props.modelValue
  },
  set(value) {
    emit('update:modelValue', extend(true, [], value))
  }
})

const fileRejected = (errs) => {
  errs.forEach(err => {
    let message = ''
    if (err.failedPropValidation == 'max-file-size') { // noinspection JSCheckFunctionSignatures
      message = `文件 ${err.file.name} 大小超过限制, 请上传小于 ${format.humanStorageSize(computedMaxFileSize.value)} 的文件`
    }
    if (err.failedPropValidation == 'accept') message = `文件 ${err.file.name} 类型不符合要求, 请上传 ${props.accept} 类型的文件`
    if (!!message) {
      $q.notify({
        message: message,
        type: 'negative',
        position: 'top'
      })
    }
  })
}

// 已上传文件列表
const uploadedFiles = ref([])
const loading = ref(false)

// 上传成功触发
const fileUploaded = (info) => {
  const {xhr} = info
  const res = JSON.parse(xhr.response)
  // 去除空字符串
  file_id_list.value = file_id_list.value.filter(item => item)
  const id = res.data.id
  if (file_id_list.value.indexOf(id) === -1) {
    file_id_list.value.push(id)
  }
  emit('update:modelValue', extend(true, [], file_id_list.value))
  // 上传成功后清空已上传文件列表
  uploader.value.removeUploadedFiles()
  if (!props.isImport) {
    $q.notify({
      message: `文件 ${res.data.file_name} 上传成功`,
      type: 'positive',
      timeout: 3000,
      progress: true
    })
  }
  emit('afterUploaded')
}

// 上传失败触发
const fileFailed = (info) => {
  const {files, xhr} = info
  let responseObj = JSON.parse(xhr.response)
  let errMsg = responseObj.file[0] || '';
  for (let i = 0; i < files.length; i++) {
    const file = files[i]
    uploader.value.removeFile(file)
    $q.notify({
      message: `文件 ${file.name} 上传失败,${errMsg}`,
      type: 'negative',
      position: 'top',
      timeout: 3000,
      progress: true
    })
  }
}

// 添加文件, 上传前触发 done
const beforeUpload = (files) => {
  for (let i = 0; i < files.length; i++) {
    // 检查files是否包含特殊字符 ~!@#$%^&*/ 或者 空格
    const file = files[i]
    if (/[\s&?=/]/.test(file.name)) {
      uploader.value.removeFile(file)
      $q.notify({
        message: `文件 ${file.name} 名称不合法，不能包含特殊字符 &?=/ 或者 空格`,
        type: 'negative',
        position: 'top',
        timeout: 3000,
        progress: true
      })
    }
    // 检查文件是否已经存在
    if (uploadedFiles.value.map(item => item.file_name).indexOf(file.name) !== -1) {
      uploader.value.removeFile(file)
      $q.dialog({
        title: '文件已存在',
        message: `文件 ${file.name} 已存在，是否覆盖？`,
        cancel: true,
        persistent: false
      }).onOk(() => {
        try {
          const file_id = uploadedFiles.value.filter(item => item.file_name === file.name)[0].id
          api_delete_files({id: file_id}).then(() => {
            uploadedFiles.value = uploadedFiles.value.filter(item => item.file_name !== file.name)
            uploader.value.addFiles([file])
          })
        } catch (e) {
        }
      })
    }
  }
}


// 检查文件 done
// const checkFile = (files) => {
// }

// 检查文件 done
const pickFiles = () => {
  uploader.value.pickFiles()
}

// 删除文件触发 done
const fileRemoved = (files) => {
  emit('fileRemoved', files)
}

// 获取文件列表
const getFileList = debounce(() => {
  let val = props.modelValue
  if (!val || val.length == 0) {
    uploadedFiles.value = []
    return
  }
  // 如果val的第一项不是数字, 说明是文件名而不是id
  let params = Array.isArray(val) ? val.join(',') : val
  loading.value = true
  if (is.number(val[0]) || /^\d+$/.test(val[0])) {
    api_get_file_list({id__in: params}).then(res => {
      uploadedFiles.value = res.data.data.results
      emit('update:modelValue', uploadedFiles.value.map(item => item.id))
      loading.value = false
    }).catch(() => {
      loading.value = false
    })
  } else {
    // 将文件名中的 [ ] ' " 空格 等特殊字符替换
    params = params.replace(/[\[\]'"\s]/g, '')
    if (params == "") {
      uploadedFiles.value = []
      emit('update:modelValue', uploadedFiles.value)
      loading.value = false
      return
    }
    api_get_file_list({file_name__in: params}).then(res => {
      uploadedFiles.value = res.data.data.results
      // 去重并取最后一个
      uploadedFiles.value.reverse()
      uploadedFiles.value = uploadedFiles.value.filter((item, index, arr) => {
        return arr.findIndex(item2 => item2.file_name === item.file_name) === index
      })
      emit('update:modelValue', uploadedFiles.value.map(item => item.id))
      loading.value = false
    }).catch(() => {
      loading.value = false
    })
  }
}, 200, true)

watch(() => props.modelValue, () => {
  getFileList()
}, {immediate: true, deep: true})

defineExpose({
  uploadedFiles, pickFiles
})

const uploadHandle = () => {
  uploader.value.pickFiles()
}

const isShowUploadHeader = computed(() => {
  if (props.isImport) return false
  return !props.isReduce;

})


</script>

<style scoped>

</style>